Skip to content

fix(orchestrate): graceful IPC disconnect to prevent race condition#164

Merged
laynepenney merged 1 commit intomainfrom
fix/reader-disconnect-race
Jan 26, 2026
Merged

fix(orchestrate): graceful IPC disconnect to prevent race condition#164
laynepenney merged 1 commit intomainfrom
fix/reader-disconnect-race

Conversation

@laynepenney
Copy link
Copy Markdown
Collaborator

@laynepenney laynepenney commented Jan 26, 2026

Summary

  • Fix race condition where readers/workers report "disconnected unexpectedly" even after successful completion
  • Fix non-interactive mode (-P flag) where readline was closing prematurely
  • Change socket.destroy() to socket.end() with proper drain handling
  • Add 1-second timeout as safety fallback

Problem 1: IPC Disconnect Race

The IPC client was using socket.destroy() which immediately closes the socket without waiting for pending writes to complete. This caused a race condition:

  1. Child agent sends task_complete message
  2. Child immediately calls disconnect() which destroys the socket
  3. Server receives close event before processing the completion message
  4. Reader/worker marked as "disconnected unexpectedly"

Problem 2: Non-Interactive Mode Exit

In non-interactive mode (-P flag), readline was still being created. When stdin closed (non-TTY mode), readline's close event triggered handleExit() before the prompt was processed.

Solution

IPC Client

Use socket.end() with a drain wait to ensure pending writes complete:

async disconnect(): Promise<void> {
  await new Promise<void>((resolve) => {
    const timeout = setTimeout(() => {
      socket.destroy();
      resolve();
    }, 1000);

    socket.once('close', () => {
      clearTimeout(timeout);
      resolve();
    });

    socket.end(); // Graceful close
  });
}

Non-Interactive Mode

Skip readline creation when -P flag is used:

const isNonInteractive = Boolean(options.prompt);
if (!useInkUi && !isNonInteractive) {
  // Create readline only for interactive mode
}

Test plan

  • Build passes
  • All 2070 tests pass
  • Orchestrate tests pass (14 tests)
  • Non-interactive mode works: codi -P "what is 2+2?" → "Four"
  • Readers spawn and errors are reported properly (not "disconnected unexpectedly")

🤖 Generated with Claude Code

The IPC client was using socket.destroy() which immediately closes the
socket without waiting for pending writes to complete. This caused a
race condition where:

1. Child agent sends task_complete message
2. Child immediately calls disconnect() which destroys the socket
3. Server receives 'close' event before processing the completion message
4. Reader/worker marked as "disconnected unexpectedly"

Fix: Use socket.end() with a drain wait and 1-second timeout to ensure
pending writes complete before closing the connection.

Also fixes non-interactive mode (-P flag) where readline was being
created and immediately closing, triggering premature exit before the
prompt could be processed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@laynepenney laynepenney force-pushed the fix/reader-disconnect-race branch from 14bac09 to 015f62a Compare January 26, 2026 02:22
@laynepenney laynepenney merged commit 186a5e6 into main Jan 26, 2026
3 checks passed
@laynepenney laynepenney deleted the fix/reader-disconnect-race branch January 26, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant